﻿#include "./uiobject.h"
#include "./view.h"
#include "./winview.h"
#include "./hovertrigger.h"

using namespace MiniUI;
using namespace MiniUI::Type;

UIObject::UIObject(const char* szType)
: Object(szType)
, m_anchors(ANCHOR_LEFT | ANCHOR_TOP)
, m_blendValue(0xFF)
, m_bVisible(true)
, m_bEnable(true)
, m_bSelected(false)
, m_bTransparent(false)
, m_cursor(CT_NONE)
, m_customCursor(nullptr)
, m_bgColor(0)
, m_shape(SHAPE_RECT)
, m_pParent(nullptr)
, m_pWinView(nullptr)
, m_pNext(nullptr)
, m_pPrev(nullptr)
{
}

UIObject::~UIObject()
{
    if (m_pParent)
    {
        UIObject* pPrev = m_pPrev;
        UIObject* pNext = m_pNext;
        if (pPrev)
        {
            pPrev->m_pNext = m_pNext;
        }
        else
        {
            m_pParent->m_pChild = pNext;
        }
        if (pNext)
        {
            pNext->m_pPrev = pPrev;
        }
    }
}

void UIObject::SetWinView(WinView* pWinView)
{
    m_pWinView = pWinView;
}

UIObject* UIObject::GetNext() const
{
    return m_pNext;
}

UIObject* UIObject::GetPrev() const
{
    return m_pPrev;
}

View* UIObject::GetParent() const
{
    return m_pParent;
}

WinView* UIObject::GetWinView() const
{
    return m_pWinView;
}

void UIObject::GetPos(MiniPoint& pt) const
{
    pt = m_pos;
}

void UIObject::SetPos(const MiniPoint& pt, bool bUpdateMargin)
{
    if (pt != m_pos)
    {
        ObjectGuard(this);
        MiniPoint ptOld = m_pos;
        m_pos = pt;
        DoMove(ptOld, pt);
        if (bUpdateMargin)
        {
            UpdateMargin(m_pParent);
        }
    }
}

void UIObject::GetSize(MiniSize& size) const
{
    size = m_size;
}

void UIObject::SetSize(const MiniSize& size, bool bUpdateMargin)
{
    if (size != m_size)
    {
        ObjectGuard(this);
        MiniSize szOld = m_size;
        m_size = size;
        DoResize(szOld, size);
        if (bUpdateMargin)
        {
            UpdateMargin(m_pParent);
        }
    }
}

void UIObject::GetRect(MiniRect& rect) const
{
    rect.SetRect(m_pos, m_pos + m_size);
}

void UIObject::SetRect(const MiniRect& rect, bool bUpdateMargin)
{
    ObjectGuard(this);
    bool bChange = false;
    MiniRect rc = rect;
    if (rc.Size() != m_size)
    {
        MiniSize szOld = m_size;
        m_size = rc.Size();
        DoResize(szOld, m_size);
        bChange = true;
    }
    if (rect.TopLeft() != m_pos)
    {
        MiniPoint ptOld = m_pos;
        m_pos = rc.TopLeft();
        DoMove(ptOld, m_pos);
        bChange = true;
    }
    if (bChange && bUpdateMargin)
    {
        UpdateMargin(m_pParent);
    }
}

void UIObject::GetMargin(MiniRect& rcMargin) const
{
    rcMargin = m_margin;
}

void UIObject::SetMargin(const MiniRect& rcMargin)
{
    if (rcMargin != m_margin)
    {
        ObjectGuard(this);
        m_margin = rcMargin;
        MiniPoint ptObj = m_pos;
        MiniSize szObj = m_size;
        UpdateRect(m_pParent);
        if (szObj != m_size)
        {
            DoResize(szObj, m_size);
        }
        if (ptObj != m_pos)
        {
            DoMove(ptObj, m_pos);
        }
    }
}

void UIObject::UpdateRect(View* pParent)
{
    if (pParent)
    {
        Layout* pLayout = pParent->GetLayout();
        bool bLockLayout = pParent->IsLockLayout();
        if (pLayout && !bLockLayout)
        {
            MiniRect rcParent;
            pParent->GetRect(rcParent);
            rcParent.MoveToXY(0, 0);
            pLayout->UpdateRect(rcParent, this);
        }
    }
}

void UIObject::UpdateMargin(View* pParent)
{
    if (pParent)
    {
        Layout* pLayout = pParent->GetLayout();
        bool bLockLayout = pParent->IsLockLayout();
        if (pLayout && !bLockLayout)
        {
            MiniRect rcParent;
            pParent->GetRect(rcParent);
            rcParent.MoveToXY(0, 0);
            pLayout->UpdateMargin(rcParent, this);
        }
    }
}

void UIObject::GetClientRect(MiniRect& rect) const
{
    rect = MiniRect(0, 0, m_size.cx, m_size.cy);
    if (m_pWinView && m_pParent)
    {
        rect += m_pos;
        View* pView = m_pParent;
        while (pView != m_pWinView)
        {
            MiniPoint ptObj;
            pView->GetPos(ptObj);
            rect += ptObj;
            pView = pView->m_pParent;
        }
    }
}

void UIObject::GetClientCursorPos(MiniPoint& pt) const
{
    pt.SetPoint(0, 0);
    if (m_pWinView)
    {
        m_pWinView->GetScreenCursorPos(pt);
        ScreenToClient(pt);
    }
}

void UIObject::ClientToScreen(MiniPoint& pt) const
{
    if (this == m_pWinView)
    {
        pt += m_pos;
    }
    else if (m_pWinView && m_pParent)
    {
        pt += m_pos;
        View* pView = m_pParent;
        while (pView != m_pWinView)
        {
            MiniPoint ptObj;
            pView->GetPos(ptObj);
            pt += ptObj;
            pView = pView->m_pParent;
        }
        pt += m_pWinView->m_pos;
    }
}

void UIObject::ScreenToClient(MiniPoint& pt) const
{
    if (this == m_pWinView)
    {
        pt -= m_pos;
    }
    else if (m_pWinView && m_pParent)
    {
        pt -= m_pos;
        View* pView = m_pParent;
        while (pView != m_pWinView)
        {
            MiniPoint ptObj;
            pView->GetPos(ptObj);
            pt -= ptObj;
            pView = pView->m_pParent;
        }
        pt -= m_pWinView->m_pos;
    }
}

void UIObject::SetBlendValue(uint8 value)
{
    if (m_blendValue != value)
    {
        ObjectGuard(this);
        m_blendValue = value;
        Update();
    }
}

uint8 UIObject::GetBlendValue()
{
    return m_blendValue;
}

void UIObject::SetVisible(bool bVisible)
{
    if (bVisible != m_bVisible)
    {
        ObjectGuard(this);
        m_bVisible = bVisible;
        Update();
        DoVisibleChange();
    }
}

bool UIObject::GetVisible()
{
    return m_bVisible;
}

void UIObject::SetEnable(bool bEnable)
{
    if (m_bEnable != bEnable)
    {
        ObjectGuard(this);
        m_bEnable = bEnable;
        Update();
    }
}

bool UIObject::GetEnable()
{
    return m_bEnable;
}

void UIObject::SetSelected(bool bSelected)
{
    if (m_bSelected != bSelected)
    {
        ObjectGuard(this);
        m_bSelected = bSelected;
        Update();
    }
}

bool UIObject::GetSelected()
{
    return m_bSelected;
}

void UIObject::SetTransparent(bool bTransparent)
{
    if (m_bTransparent != bTransparent)
    {
        m_bTransparent = bTransparent;
    }
}

bool UIObject::GetTransparent()
{
    return m_bTransparent;
}

void UIObject::SetCursor(CursorType cursor, void* customCursor)
{
    m_cursor = cursor;
    m_customCursor = customCursor;
}

CursorType UIObject::GetCursor()
{
    return m_cursor;
}

void UIObject::SetBackgroundColor(const MiniARGB& color)
{
    if (m_bgColor != color)
    {
        ObjectGuard(this);
        m_bgColor = color;
        Update();
    }
}

void UIObject::GetBackgroundColor(MiniARGB& color)
{
    color = m_bgColor;
}

void UIObject::SetShape(SHAPE shape)
{
    m_shape = shape;
}

UIObject::SHAPE UIObject::GetShape()
{
    return m_shape;
}

void UIObject::SetAnchors(uint8 anchors)
{
    if (m_anchors != anchors)
    {
        ObjectGuard(this);
        m_anchors = anchors;
        UpdateMargin(m_pParent);
    }
}

uint8 UIObject::GetAnchors()
{
    return m_anchors;
}

bool UIObject::CheckEnable()
{
    bool bEnable = true;
    UIObject* pObj = this;
    while (pObj)
    {
        bEnable = pObj->GetEnable();
        if (!bEnable)
        {
            return false;
        }
        pObj = pObj->GetParent();
    }
    return true;
}

bool UIObject::CheckVisible()
{
    bool bEnable = true;
    UIObject* pObj = this;
    while (pObj)
    {
        bEnable = pObj->GetVisible();
        if (!bEnable)
        {
            return false;
        }
        pObj = pObj->GetParent();
    }
    return true;
}

bool UIObject::SetFocus(bool bFocus)
{
	if (m_pWinView)
    {
        ObjectGuard(this);
		if (bFocus)
		{
			return m_pWinView->SetFocusObject(this);
		}
		else
		{
			if (m_pWinView->GetFocusObject() == this)
			{
				return m_pWinView->SetFocusObject(nullptr);
			}
		}
	}
	return false;
}

bool UIObject::IsFocused()
{
	if (m_pWinView)
	{
		return m_pWinView->GetFocusObject() == this;
	}
	return false;
}

bool UIObject::HitTest(const MiniPoint& pt)
{
    if (!m_bVisible)
    {
        return false;
    }
    MiniRect rect(m_pos, m_pos + m_size);
    if (rect.PtInRect(pt))
    {
        return true;
    }
    else
    {
        return false;
    }
}

void UIObject::Update()
{
    ObjectGuard(this);
    MiniRect rc;
    rc.SetRect(0, 0, m_size.cx, m_size.cy);
    DoChange(rc);
}

void UIObject::DoClick(const MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    ObjectGuard(this);
    if (CheckEnable() && !Click(pt, button, keyState, bHandled))
    {
        if (m_bTransparent)
        {
            bHandled = false;
        }
        else
        {
            DoFireEvent(OnClickEventFunc, OnClick, (this, pt, button, keyState));
        }
    }
}

void UIObject::DoDblClk(const MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    ObjectGuard(this);
    if (CheckEnable() && !DblClk(pt, button, keyState, bHandled))
    {
        if (m_bTransparent)
        {
            bHandled = false;
        }
        else
        {
            DoFireEvent(OnClickEventFunc, OnDblClick, (this, pt, button, keyState));
        }
    }
}

void UIObject::DoMouseDown(const MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    ObjectGuard(this);
    if (CheckEnable() && !MouseDown(pt, button, keyState, bHandled))
    {
        if (m_bTransparent)
        {
            bHandled = false;
        }
        else
        {
            DoFireEvent(OnMouseEventFunc, OnMouseDown, (this, pt, button, keyState));
        }
    }
}

void UIObject::DoMouseUp(const MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    ObjectGuard(this);
    if (CheckEnable() && !MouseUp(pt, button, keyState, bHandled))
    {
        if (m_bTransparent)
        {
            bHandled = false;
        }
        else
        {
            DoFireEvent(OnMouseEventFunc, OnMouseUp, (this, pt, button, keyState));
        }
    }
}

void UIObject::DoMouseMove(const MiniPoint& pt, uint keyState, bool& bHandled)
{
    ObjectGuard(this);
    if (CheckEnable() && !MouseMove(pt, keyState, bHandled))
    {
        if (m_bTransparent)
        {
            bHandled = false;
        }
        else
        {
            DoFireEvent(OnMouseMoveEventFunc, OnMouseMove, (this, pt, keyState));
        }
    }
}

void UIObject::DoMouseHover()
{
    ObjectGuard(this);
    MouseHover();
    if (CheckEnable() && !m_bTransparent)
    {
        DoFireEvent(OnNotifyEventFunc, OnMouseHover, (this));
    }
}

void UIObject::DoMouseEnter()
{
    ObjectGuard(this);
    MouseEnter();
    if (!m_bTransparent && m_pWinView)
    {
        m_pWinView->GetHoverTrigger()->Bind(this);
    }
    if (CheckEnable() && !m_bTransparent)
    {
        DoFireEvent(OnNotifyEventFunc, OnMouseEnter, (this));
    }
}

void UIObject::DoMouseLeave()
{
    ObjectGuard(this);
    MouseLeave();
    if (m_pWinView)
    {
        m_pWinView->GetHoverTrigger()->Unbind(this);
    }
    if (CheckEnable() && !m_bTransparent)
    {
        DoFireEvent(OnNotifyEventFunc, OnMouseLeave, (this));
    }
}

void UIObject::DoMouseWheel(const MiniPoint& pt, uint keyState, int16 zDelta, bool& bHandled)
{
    ObjectGuard(this);
    if (CheckEnable() && !MouseWheel(pt, keyState, zDelta, bHandled))
    {
        if (m_bTransparent)
        {
            bHandled = false;
        }
        else
        {
            if (!OnMouseWheel.IsEmpty())
            {
                DoFireEvent(OnMouseWheelEventFunc, OnMouseWheel, (this, pt, zDelta, keyState));
            }
            else
            {
                bHandled = false;
            }
        }
    }
}

void UIObject::DoChange(const MiniRect& rect)
{
    ObjectGuard(this);
    Change(rect);
    DoFireEvent(OnChangeEventFunc, OnChange, (this, rect));
}

void UIObject::DoVisibleChange()
{
    ObjectGuard(this);
    VisibleChange(m_bVisible);
    UpdateRect(m_pParent);
    DoFireEvent(OnNotifyEventFunc, OnVisibleChange, (this));
}

void UIObject::DoFocusChange(bool bFocus)
{
	ObjectGuard(this);
	FocusChange(bFocus);
	DoFireEvent(OnFocusEventFunc, OnFocus, (this, bFocus));
}

void UIObject::DoMove(const MiniPoint& ptSrc, const MiniPoint& ptDst)
{
    ObjectGuard(this);
    Move(ptSrc, ptDst);
    DoFireEvent(OnMoveEventFunc, OnMove, (this, ptSrc, ptDst));
}

void UIObject::DoResize(const MiniSize& szOld, const MiniSize& szNew)
{
    ObjectGuard(this);
    Resize(szOld, szNew);
    DoFireEvent(OnResizeEventFunc, OnResize, (this, szOld, szNew));
}

void UIObject::DoParentChange()
{
    ObjectGuard(this);
    UpdateRect(m_pParent);
}

bool UIObject::Click(const MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    bHandled = true;
    return false;
}

bool UIObject::DblClk(const MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    bHandled = true;
    return false;
}

bool UIObject::MouseDown(const MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    bHandled = true;
    return false;
}

bool UIObject::MouseUp(const MiniPoint& pt, MouseButton button, uint keyState, bool& bHandled)
{
    bHandled = true;
    return false;
}

bool UIObject::MouseMove(const MiniPoint& pt, uint keyState, bool& bHandled)
{
    if (m_pWinView)
    {
        m_pWinView->SetCursor(m_cursor, m_customCursor);
    }
    bHandled = true;
    return false;
}

void UIObject::MouseHover()
{
}

void UIObject::MouseEnter()
{
}

void UIObject::MouseLeave()
{
}

bool UIObject::MouseWheel(const MiniPoint& pt, uint keyState, int16 zDelta, bool& bHandled)
{
    bHandled = true;
    return false;
}

void UIObject::Change(const MiniRect& rect)
{
    NotifyParentChange(rect);
}

void UIObject::Move(const MiniPoint& ptSrc, const MiniPoint& ptDst)
{
    NotifyParentMove(ptSrc, ptDst);
}

void UIObject::Resize(const MiniSize& szOld, const MiniSize& szNew)
{
    NotifyParentResize(szOld, szNew);
}

void UIObject::VisibleChange(bool bVisible)
{
}

void UIObject::FocusChange(bool bFocus)
{
}

void UIObject::ShapeClip(Graphics& graphics)
{
    MiniRect rcClip(0, 0, m_size.cx, m_size.cy);
    switch (m_shape)
    {
    case SHAPE_RECT:
        graphics.SetRectClip(rcClip, true);
        break;
    case SHAPE_CIRCLE:
        graphics.SetCircleClip(rcClip, true);
        break;
    }
}

void UIObject::NotifyParentChange(const Type::MiniRect& rect)
{
    if (m_pParent)
    {
        m_pParent->ChildChange(this, rect);
    }
}

void UIObject::NotifyParentMove(const Type::MiniPoint& ptSrc, const Type::MiniPoint& ptDst)
{
    if (m_pParent)
    {
        m_pParent->ChildMove(this, ptSrc, ptDst);
    }
}

void UIObject::NotifyParentResize(const Type::MiniSize& szOld, const Type::MiniSize& szNew)
{
    if (m_pParent)
    {
        m_pParent->ChildResize(this, szOld, szNew);
    }
}